/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package itemrecovertool;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 *
 * @author Matt
 */
public class Main {
    
    private final static String ENCODING = "UTF-16LE";
    private final static byte DELIM = (byte)0xDE;
    private final static byte SEP = (byte)0xFF;
    private final static byte NIL = (byte)0x00;
    
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws Exception {
        RandomAccessFile file = new RandomAccessFile("0.svt", "r");
        
        file.seek(4);
        //System.out.println(Short.reverseBytes(file.readShort()));
        
        file.seek(6);
        String playerClass = readString(file, DELIM);
        
        file.seek(file.getFilePointer()+83);
        String playerName = readString(file, DELIM);
        
        //String playerName = readString(file, DELIM);
        
        System.out.println("'"+playerClass+"'");
        System.out.println("'"+playerName+"'");
        
        //file.seek(43);
        
        int gold = -1;
        //a group of exactly 12 FF bytes proceeds the gold int
        boolean success = moveToByteGroup(file, SEP, 12, true);
        if (success) {
            long i = file.getFilePointer();
            file.seek(i-8);
            
            //byte[] b = new byte[4];
            //b[0] = file.readByte(); b[1] = file.readByte();
            //b[2] = file.readByte(); b[3] = file.readByte();
            //reverse(b);
            gold = Integer.reverseBytes(file.readInt());
            //gold = bytesToInt(b);
            System.out.println(gold+" gp");
        } else {
            System.err.println("Couldn't find the player's money.");
        }
        
        
        //now find items...
        //search for DELIMs, if it is immediately after SEP then we've reached the end of the inventory
        
        //D . . . . . N A M E . . . D
        //Delim, 9 bytes, name, 6 bytes, delim
        
        byte prev = NIL;
        success = moveToByte(file, DELIM, true);
        //D . . . . . N A M E . . . D
        //^
        while (success && (prev=previousByte(file)) != SEP) {
            //if DELIM proceeds anything other than NIL, it is probably not an item
            if (prev == NIL) {
                //TODO: EOF check?
                file.seek(10 + file.getFilePointer());
                //D . . . . . N A M E . . . D
                //            ^
                System.out.println("DISPLAYNAME: "+readString(file, DELIM));
                //D . . . . . N A M E . . . D
                //                    ^

                //seek + 5 doesn't work with certain affixes, just search for DELIM instead
                moveToByte(file, DELIM, false);
                System.out.println(file.getFilePointer());
                //D . . . . . N A M E . . . D
                //                            ^

                //seek to location
                file.seek(40 + file.getFilePointer());
                System.out.println("Item Location: "+bytesToInt(asInt(file.readByte())));
                System.out.println();
                
            } else { //let's keep looking
                file.readByte(); //skip this delim byte
            }
            
            //start over again, seeking to the start of the next delim
            success = moveToByte(file, DELIM, true);
        }
        
        //System.out.println((byte)0x7f);
        //System.out.println(new String(new byte[]{0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0}, ENCODING));
        
        
        
        /*byte[] b = new byte[30];
        for (int i=0; i<b.length;i++)
            b[i] = file.readByte();
        System.out.println(Arrays.toString(b));
        String str = new String(b, "UTF-16LE");
        System.out.println("'"+str.trim()+"'");
        System.out.println((byte)0xDE == b[28]);*/
        
        
        //(byte)0x02

        file.close();
    }
    
    private static boolean moveToByte(RandomAccessFile file, byte b) throws IOException {
        return moveToByteGroup(file, b, 1, false, true);
    }
    
    private static boolean moveToByte(RandomAccessFile file, byte b, boolean seekBack) throws IOException {
        return moveToByteGroup(file, b, 1, false, seekBack);
    }
    
    /** Returns true if the current pointer immediately follows the given byte. */
    private static byte previousByte(RandomAccessFile file) throws IOException {
        long len = file.getFilePointer();
        file.seek(len-1);
        return file.readByte();
    }
    
    private static boolean moveToByteGroup(RandomAccessFile file, byte b, 
            int x, boolean exact) throws IOException {
        return moveToByteGroup(file, b, x, exact, true);
    }
    
    /**
     * From the current filePointer, seeks forward until exactly 'x' number of
     * bytes 'b' are found together. False is returned if the EOF was reached
     * before finding any groups (the pointer is reset to it's old value).
     * If exact is false, it is >= X as opposed to exactly X. 
     * If seekBack is true, the pointer will be placed directly before the byte 
     * group.
     * 
     * Note: looking for byte 0x01 might be buggy
     */
    private static boolean moveToByteGroup(RandomAccessFile file, byte b, int x, 
            boolean exact, boolean seekBack) throws IOException {
        if (x==0)
            throw new IllegalArgumentException("x can't be 0");        
        long start = file.getFilePointer();
        //search through the rest of the file
        int count = 0;
        long groupStart = 0;
        boolean skipGroup = false;
        
        while (file.getFilePointer() < file.length()) {
            //get the next byte
            byte next = file.readByte();
            //is that byte what we are looking for?
            //if we are skipping a group, ignore
            if (next == b && !skipGroup) {
                //it's the start of a group
                if (count==0)
                    groupStart = file.getFilePointer()-1;
                count++;
                //if we have found exactly X of them
                if (count == x) {
                    //don't search for exactly X, so it doesn't matter what's next
                    //just move & return
                    if (!exact) {
                        if (seekBack)
                            file.seek(groupStart);
                        return true;
                    }
                    //otherwise.. if it is exact
                    //and it's not the EOF, let's lookahead
                    else if (file.getFilePointer() < file.length()) {
                        byte lookahead = file.readByte();
                        //yup, we had found the end of a group
                        //reset the pointer to the start of the group
                        if (lookahead != b) {
                            if (seekBack)
                                file.seek(groupStart);
                            return true;
                        } else {
                            //it's a group, but not the right size.. ignore it
                            //don't bother moving the pointer back one, we'll skip this anyways
                            skipGroup = true;
                        }
                    } 
                    //we've reached the EOF with exactly X.. it's a group
                    else {
                        if (seekBack)
                            file.seek(groupStart);
                        return true;
                    }
                    
                }
            } else if (next != b) {
                //it's not the byte we are looking for... reset everything
                skipGroup = false;
                count = 0;
                groupStart = 0;
            }
        }
        if (seekBack)
            file.seek(start);
        return false;
    }
    
    private static String readString(RandomAccessFile file, byte end) throws IOException {
        byte[] b = readBytes(file, end, true, false);
        return new String(b, ENCODING);
    }
    
    /** 
     * Reads bytes until the 'end' byte is reached 
     * (won't be included in the result) or until the pointer passes two
     * '00' bytes. Ascii characters include 0x20 to 0x7E, and 0xA1 to 0xFF.
     * If trim is true, the last 0x00 will be dropped from the array.
     */
    private static byte[] readBytes(RandomAccessFile file, byte end, boolean ascii, boolean trim) throws IOException {
        List<Byte> bytes = new ArrayList<Byte>();
        
        long start = file.getFilePointer();
        
        byte nextByte = file.readByte();
        byte lastByte = (byte)0xFF;
        //System.out.println(nextByte+" "+end);
        while (nextByte != end) {
            if (!ascii || isValidStrByte(nextByte)) {
                if (lastByte==0 && nextByte==0)
                    break;
                bytes.add(nextByte);
                lastByte = nextByte;
            }
            nextByte = file.readByte();
        }
        if (trim && !bytes.isEmpty() && bytes.get(bytes.size()-1)==NIL)
            bytes.remove(bytes.size()-1);
        byte[] ar = new byte[bytes.size()];
        for (int i=0; i<ar.length; i++)
            ar[i] = bytes.get(i);
        file.seek( start+ar.length );
        return ar;
    }
    
    private static boolean isValidStrByte(byte b) {
        return ((int)b >= -95 && (int)b <= 0) 
                || ((int)b >= 32 && (int)b <= 126);
    }//0x20 to 0x7E and 0xA1 to 0xFF.
    
    /**
     * Reverses a byte array
     */
    public static int toInt(byte[] b) {
        int left  = 0;          // index of leftmost element
        int right = b.length - 1; // index of rightmost element
        while (left < right) {
            // exchange the left and right elements
            byte temp = b[left];
            b[left] = b[right];
            b[right] = temp;

            // move the bounds toward the center
            left++;
            right--;
        }

        StringBuffer buf = new StringBuffer();
        buf.append("#");
        for (byte by : b) {
            String hx = Integer.toHexString((int)by);
            buf.append(hx);
        }
        return Integer.decode(buf.toString());
    }
    
    private static byte[] asInt(byte b) {
        return new byte[] { (byte)0x00, (byte)0x00, (byte)0x00, b };
    }
    
    private static void reverse(byte[] b) {
        //reverse the array
        int left = 0;
        int right = b.length - 1;
        while (left < right) {
            byte temp = b[left]; b[left] = b[right]; b[right] = temp;
            left++;
            right--;
        }
    }
    
    static byte[] intToBytes(int i){
        ByteBuffer bb = ByteBuffer.allocate(4); 
        bb.putInt(i); 
        return bb.array();
    }
    
    static int bytesToInt(byte[] intBytes) {
        ByteBuffer bb = ByteBuffer.wrap(intBytes);
        return bb.getInt();
    }
}
